package org.vaadin.nf4v.core.util;

import org.vaadin.nf4v.core.navigation.event.NavigationListener;
import org.vaadin.nf4v.core.navigation.NavigationRequest;

import java.io.Serializable;
import java.util.*;

/**
 * Flash scope emulation implementation, like in Rails or Grails.
 * Provides temporal storage which will be available during two requests.
 *
 * Flash operates as usual Map.
 *
 * Actual implementation is a list of two maps.
 * When you add new values they are actually added into last map.
 * After navigation first map is removed, new empty map is added at the end of the chain.
 * So after 2 navigation's map with objects set in first navigation being removed.
 */
public class NavigationFlash implements NavigationListener, Map<String, Object>, Serializable {

    private LinkedList<Map<String, Object>> flash = new LinkedList<Map<String, Object>>();

    public NavigationFlash() {
        // initial "request scope"
        flash.add(new HashMap<String, Object>());
    }

    // todo this shouldn't be public
    @Override
    public void onNavigation(NavigationRequest request) {
        flash.add(new HashMap<String, Object>());
        if (flash.size() > 2) flash.removeFirst();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear() {
        while (flash.size()!=1){
            flash.removeFirst();
        }
        flash.getLast().clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsKey(Object key) {
        for (Map map : flash) {
            if (map.containsKey(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsValue(Object value) {
        for (Map map : flash) {
            if (map.containsValue(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<Entry<String, Object>> entrySet() {
        Set<Entry<String, Object>> set = new HashSet<Entry<String, Object>>();
        for (Map<String, Object> map : flash) {
            set.addAll(map.entrySet());
        }
        return set;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object get(Object key) {
        for (Map map : flash) {
            if (map.containsKey(key)) {
                return map.get(key);
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty() {
        for (Map map : flash) {
            if (!map.isEmpty()) {
                return false;
            }
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> keySet() {
        Set<String> set = new HashSet<String>();
        for (Map<String, Object> map : flash) {
            set.addAll(map.keySet());
        }
        return set;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object put(String key, Object value) {
        remove(key); // clean in all collections
        return flash.getLast().put(key, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void putAll(Map<? extends String, ? extends Object> m) {
        for (String key : m.keySet()) {
            remove(key);
        }
        flash.getLast().putAll(m);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object remove(Object key) {
        for (Map map : flash) {
            if (map.containsKey(key)) {
                // other methods ensure that only one map contain value
                return map.remove(key);
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        int size = 0;
        for (Map map : flash) {
            size += map.size();
        }
        return size;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Object> values() {
        Collection<Object> set = new ArrayList<Object>();
        for (Map<String, Object> map : flash) {
            set.addAll(map.values());
        }
        return set;
    }
}
